home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 March / macformat-022.iso / Shareware City / Developers / src / rfc-1123-822-date-formatter-c / RFC1123Date.c next >
Encoding:
C/C++ Source or Header  |  1994-11-23  |  12.8 KB  |  430 lines  |  [TEXT/KAHL]

  1. /*
  2.  *    SecsToRFC1123Date()
  3.  *    RFC1123DateToSecs()
  4.  *
  5.  *    In this, we use the system international routines to convert between RFC 1123
  6.  *    (and RFC 822) compliant date/time strings and numeric dates and times. Requires
  7.  *    accompanying file with 'itl0', 'itl1', 'itl4', 'FMAT', and 'ZON#' resources.
  8.  *
  9.  *    © Pete Resnick, 11 January 1994. Feel free to use these routines, but give me
  10.  *    proper credit in documentation if you do. I can be reached on the Internet
  11.  *    at the address "resnick@uiuc.edu".
  12.  *
  13.  *    The 'FMAT' resource was created with version 1.0.1 of Michael Hecht's FMAT Editor
  14.  *    for ResEdit, © 1993. It is available from lots of anonymous FTP archives.
  15.  *
  16.  *    Changes:
  17.  *    --------
  18.  *    12 February 1994:    Added StripString to strip comments and whitespace out.
  19.  *                            Note that StripString doesn't use nice resources, etc.
  20.  *    17 March 1994:        Added GetFormatResources and 'zon#' handling.
  21.  *     4 September 1994:    Changed to use 'ZON#' resource. 
  22.  */
  23.  
  24. #include <Errors.h>
  25. #include <Memory.h>
  26. #include <Resources.h>
  27. #include <SANE.h>
  28. #include <TextUtils.h>
  29.  
  30. /* Nice ANSI prototypes */
  31. OSErr SecsToRFC1123Date(LongDateTime *time, long gmtOffset, StringPtr string);
  32. StringToDateStatus RFC1123DateToSecs(StringPtr string, LongDateTime *time);
  33. void StripString(StringPtr fromString, StringPtr toString);
  34. OSErr GetFormatResources(NumFormatString ***numFormat, Handle *itlHandle, long *tOffset);
  35. void InitItlRsrcs(short *verbPtr, short *idPtr);
  36. void ResetItlRsrcs(short *verbPtr, short *idPtr);
  37.  
  38. /*
  39.  *    Define the resource ID of the 'itl0', 'itl1', 'itl4', 'FMAT', and 'ZON#' resources
  40.  *    for RFC 1123 dates.
  41.  */
  42. #define INTL_RESOURCES    128
  43.  
  44. /* Create new MachineLocation so there's no need to sign extend by hand */
  45. typedef struct {
  46.     Fract latitude;
  47.     Fract longitude;
  48.     struct {
  49.         long dlsDelta : 8;
  50.         long gmtDelta : 24;
  51.     } gmtFlags;
  52. } NewMachineLocation;
  53.  
  54. /* Structure in 'ZON#' resource. The Pascal string is packed and even-padded. */
  55. typedef struct {
  56.     short zoneMinutes;
  57.     Str255 zoneName;
  58. } *ZoneInfoPtr;
  59.  
  60. /* Convert time to string */
  61. OSErr SecsToRFC1123Date(time, gmtOffset, string)
  62.  
  63. LongDateTime *time;    /* Time to convert, nil if current time and PRAM offset from GMT */
  64. long gmtOffset;        /* Offset in seconds from GMT */
  65. StringPtr string;    /* String to return */
  66.  
  67. {
  68.     LongDateCvt theTime;            /* Record for current time */
  69.     StringPtr tempString;            /* Temporary string */
  70.     Handle itlHandle;                /* Handle for 'itl4' resource */
  71.     long tOff, tLen;                /* Offset and length for international table */
  72.     NumFormatStringRec **numFormat;    /* Resource containing time zone format */
  73.     FormatStatus fStatus;            /* Return status of time zone formatting */
  74.     short smVerbs[3], curIDs[3];    /* Holders for current Script Manager settings */
  75.     short gmtHundred;                /* Offset from GMT in "hundred hours" format */
  76.     extended80 gmtHundred80;        /* Extended for conversion */
  77.     double_t gmtHundred96;            /* Extended for conversion */
  78.     OSErr errCode;                    /* Return code */
  79.     
  80.     /* Get the current time and PRAM offset from GMT if the time passed was nil. */
  81.     if(time == nil) {
  82.         NewMachineLocation loc;
  83.         
  84.         ReadLocation((MachineLocation *)&loc);
  85.         gmtOffset = loc.gmtFlags.gmtDelta;
  86.  
  87.         theTime.hl.lHigh = 0L;
  88.         errCode = ReadDateTime((unsigned long *)&theTime.hl.lLow);
  89.         if(errCode != noErr)
  90.             return errCode;
  91.         time = &theTime.c;
  92.     }
  93.     
  94.     /* First set up to use our international resources */
  95.     InitItlRsrcs(smVerbs, curIDs);
  96.         
  97.     /* Convert time and date to RFC 1123 format string */
  98.     LongDateString(time, abbrevDate, string, nil);
  99.     tempString = string + (*string + 1);
  100.     LongTimeString(time, true, tempString, nil);
  101.     *string += *tempString + 1;
  102.     *tempString = ' ';
  103.     tempString = string + (*string + 1);
  104.  
  105.     /* Get the resources to format the time zone */
  106.     errCode = GetFormatResources(&numFormat, &itlHandle, &tOff);
  107.  
  108.     if(errCode == noErr) {
  109.  
  110.         /*
  111.          *    Offset from GMT is going to be in the format "±hhmm", so make gmtHundred
  112.          *    100 times number of hours plus number of minutes.
  113.          */
  114.         gmtHundred = gmtOffset / 60;
  115.         gmtHundred %= 6000;
  116.         gmtHundred = (gmtHundred % 60) + ((gmtHundred / 60) * 100);
  117.     
  118.         gmtHundred96 = gmtHundred;
  119.         x96tox80(&gmtHundred96, &gmtHundred80);
  120.     
  121.         /* Format the offset from GMT into a string */
  122.         if((FormatResultType)ExtendedToString(&gmtHundred80, *numFormat, (NumberParts *)(*itlHandle + tOff), tempString) != fFormatOK) {
  123.             errCode = paramErr;
  124.         } else {
  125.             *string += *tempString + 1;
  126.             *tempString = ' ';
  127.         }
  128.     }
  129.  
  130.     /* Reset the international resources to what they were before */
  131.     ResetItlRsrcs(smVerbs, curIDs);
  132.     return errCode;
  133. }
  134.  
  135. /* Convert string to time. See Script Manager docs for return values */
  136. StringToDateStatus RFC1123DateToSecs(string, time)
  137.  
  138. StringPtr string;    /* String to convert */
  139. LongDateTime *time;    /* Time to return */
  140.  
  141. {
  142.     long uLen, sLen;                /* Used and current string length */
  143.     DateCacheRecord dateCache;        /* Cache for String2Date and String2Time */
  144.     LongDateRec dateTime;            /* Record to hold date and time */
  145.     short smVerbs[3], curIDs[3];    /* Holders for current Script Manager settings */
  146.     short gmtMinutes;                /* offset in minutes from GMT */
  147.     StringToDateStatus status;        /* Return value */
  148.     Handle itlHandle;                /* Handle for 'itl4' resource */
  149.     Handle zoneHandle;                /* Handle for time zone info */
  150.     ZoneInfoPtr zonePtr;            /* Pointer for time zone info */
  151.     long tOff;                        /* Offset for international table */
  152.     NumFormatStringRec **numFormat;    /* Resource containing time zone format */
  153.     FormatStatus fStatus;            /* Return status of time zone formatting */
  154.     extended80 gmtHundred80;        /* Offset from GMT in ±hhmm */
  155.     double_t gmtHundred96;            /* Extended for conversion */
  156.     Str255 tempString;                /* Holder for uncommented string */
  157.     
  158.     /* First initalize cache for String2Date and String2Time */
  159.     if(InitDateCache(&dateCache) != noErr)
  160.         return cantReadUtilities;
  161.  
  162.     /* Then set up to use our international resources */
  163.     InitItlRsrcs(smVerbs, curIDs);
  164.  
  165.     /* Remove all comments and extra white space from the string */
  166.     StripString(string, tempString);
  167.     string = tempString;
  168.  
  169.     /* Get the current length of the string */
  170.     sLen = *string++;
  171.  
  172.     /* Convert the date part of the string to a LongDateRec */
  173.     status = StringToDate((Ptr)string, sLen, &dateCache, &uLen, &dateTime);
  174.     if(!(status & fatalDateTime)) {
  175.         string += ++uLen;
  176.         sLen -= uLen;
  177.     
  178.         /* Convert the time part of the string to a LongDateRec */
  179.         status = StringToTime((Ptr)string, sLen, &dateCache, &uLen, &dateTime);
  180.     }
  181.  
  182.     /* If no errors, deal with the time zone part of the string */    
  183.     if(!(status & fatalDateTime) && (sLen != 0)) {
  184.         string += uLen;
  185.         sLen -= uLen + 1;
  186.         *string = sLen;
  187.  
  188.         /* Initialize the time zone offset to zero */
  189.         gmtMinutes = 0;
  190.  
  191.         /* It could be in "±hhmm" format if the length is 5 */
  192.         if(sLen == 5L) {
  193.     
  194.             /* Get the resources to format the offset from GMT */
  195.             if(GetFormatResources(&numFormat, &itlHandle, &tOff) == noErr) {
  196.             
  197.                 /* Convert the string to an extended number */
  198.                 fStatus = StringToExtended(string, *numFormat,
  199.                                            (NumberParts *)(*itlHandle + tOff),
  200.                                            &gmtHundred80);
  201.  
  202.                 /* Clear the error portion of status */
  203.                 status &= 0x00FF;
  204.  
  205.                 /* If there are extra characters, still use the value */
  206.                 if((FormatResultType)fStatus == fSpuriousChars) {
  207.                     status |= extraneousStrings;
  208.                     fStatus = fFormatOK;
  209.                 }
  210.                 
  211.                 if((FormatResultType)fStatus == fFormatOK) {
  212.                     /* Convert the ±hhmm to minutes */
  213.     
  214.                     x80tox96(&gmtHundred80, &gmtHundred96);
  215.                     gmtMinutes = gmtHundred96;
  216.  
  217.                     gmtMinutes = (gmtMinutes % 100) + ((gmtMinutes / 100) * 60);
  218.                     status &= ~leftOverChars;
  219.                 } else {
  220.                     status |= tokenErr;
  221.                 }
  222.             } else {
  223.                 status |= cantReadUtilities;
  224.             }
  225.         } 
  226.     
  227.         /* If it wasn't in "±hhmm" format, see if it's in the zone list resource */
  228.         if((sLen != 5) || ((StringToDateStatus)(status & tokenErr) == (StringToDateStatus)tokenErr)) {
  229.             
  230.             /* Get the zone list resource */
  231.             zoneHandle = GetResource('ZON#', INTL_RESOURCES);
  232.             if(zoneHandle == nil) {
  233.                 status |= cantReadUtilities;
  234.             } else {
  235.                 
  236.                 /* Get the size of the zone list */
  237.                 sLen = InlineGetHandleSize(zoneHandle);
  238.                 zonePtr = (ZoneInfoPtr)*zoneHandle;
  239.  
  240.                 /* Look at each entry in the zone list */
  241.                 while(zonePtr < (ZoneInfoPtr)(*zoneHandle + sLen)) {
  242.                     
  243.                     /* If the zone names are the same, use that gmtMinutes value */
  244.                     if(EqualString(string, zonePtr->zoneName, false, true)) {
  245.                         gmtMinutes = zonePtr->zoneMinutes;
  246.                         status &= ~leftOverChars;
  247.                         break;
  248.                     }
  249.  
  250.                     /* Advance zonePtr to the next entry */
  251.                     zonePtr = (ZoneInfoPtr)((Ptr)zonePtr + sizeof(short) + zonePtr->zoneName[0]);
  252.  
  253.                     /* Make sure zonePtr is word-aligned; the entries are padded */
  254.                     if((long)zonePtr & 1) {
  255.                         zonePtr = (ZoneInfoPtr)((Ptr)zonePtr + 1);
  256.                     }
  257.                 }
  258.             }
  259.         }
  260.     }
  261.  
  262.  
  263.     if(!(status & fatalDateTime)) {
  264.         
  265.         /* Add any offset from GMT in minutes */
  266.         dateTime.ld.minute += gmtMinutes;
  267.         
  268.         /* Convert the record to the time */
  269.         LongDateToSeconds(&dateTime, time);
  270.     }
  271.  
  272.     /* Reset the international resources to what they were before */
  273.     ResetItlRsrcs(smVerbs, curIDs);
  274.     return status;
  275. }
  276.  
  277. /*
  278.  *    Strip out all comments and extra white space from the string. This routine has
  279.  *    the comment, escape and quote characters hardcoded instead of being in resources.
  280.  *    If we were going hog-wild with the international routines, we could use the
  281.  *    IntlTokenize() routine to do this. In the interest of code size, and because
  282.  *    IntlTokenize() can't handle nested comments, we do it the quick and dirty way.
  283.  */
  284. void StripString(fromString, toString)
  285.  
  286. StringPtr fromString;
  287. StringPtr toString;
  288.  
  289. {
  290.     short sLen;                /* The length of the string left to uncomment */
  291.     short quoteCount;        /* The number of open quotes or comments */
  292.     short escaped;            /* Flag to indicate escaped: 1 means not escaped */
  293.     StringPtr fillString;    /* A temporary pointer to the destination string */
  294.     Boolean lastWhite;        /* Flag to indicate the last character was whitespace */
  295.     Byte endChar;            /* The close quote or comment character */
  296.     
  297.     /* Get the length of the string */
  298.     sLen = *fromString++;
  299.     
  300.     fillString = toString + 1;
  301.     lastWhite = false;
  302.     quoteCount = 0;
  303.     while(sLen > 0) {
  304.     
  305.         /* Start out with no escape */
  306.         escaped = 1;
  307.  
  308.         switch (endChar = *fromString) {
  309.         
  310.             case '(' :
  311.             case '    ' :
  312.             case ' ' :
  313.             
  314.                 /* Use only one space for all contiguous whitespace and comments */
  315.                 if(!lastWhite) {
  316.                     *fillString++ = ' ';
  317.                 }
  318.                 lastWhite = true;
  319.                 
  320.                 /* Loop through until we get to the end of comments */
  321.                 do {
  322.                     switch(*fromString++) {
  323.                         case '(' :
  324.                             if(true) {
  325.                                 quoteCount += escaped;
  326.                             } else {
  327.                         case ')' :
  328.                                 quoteCount -= escaped;
  329.                             }
  330.                         default :
  331.                             escaped = 1;
  332.                             break;
  333.                         case '\\' :
  334.                             escaped ^= 1;
  335.                     }
  336.                 } while(--sLen > 0 && quoteCount > 0);
  337.                 break;
  338.             case '[' :
  339.                 endChar = ']';
  340.             case '"' :
  341.                 quoteCount = 1;
  342.             default :
  343.                 do {
  344.                     if((*fillString++ = *fromString++) == '\\')
  345.                         escaped ^= 1;
  346.                     else
  347.                         escaped = 1;
  348.                 } while(--sLen > 0 && quoteCount > 0 &&
  349.                         (*fromString != endChar || escaped == 0));
  350.                 lastWhite = false;
  351.                 quoteCount = 0;
  352.         }
  353.     }
  354.     *toString = fillString - (toString + 1);
  355. }
  356.  
  357. OSErr GetFormatResources(numFormat, itlHandle, tOffset)
  358.  
  359. NumFormatStringRec ***numFormat;
  360. Handle *itlHandle;
  361. long *tOffset;
  362.  
  363. {
  364.     OSErr errCode;
  365.     long tLen;
  366.     Byte hState;
  367.     
  368.     /* Initialize itlHandle to nil */
  369.     *itlHandle = nil;
  370.     
  371.     /* Get the format resource for the offset from GMT */
  372.     *numFormat = (NumFormatStringRec **)GetResource('FMAT', INTL_RESOURCES);
  373.     if(*numFormat != nil) {
  374.     
  375.         /* Make sure we don't lose the numFormat in the process */
  376.         hState = HGetState((Handle)*numFormat);
  377.         HNoPurge((Handle)*numFormat);
  378.         
  379.         /* Get the correct number parts table from the 'itl4' resource */
  380.         GetIntlResourceTable(smCurrentScript, smNumberPartsTable, itlHandle, tOffset, &tLen);
  381.     
  382.         /* Make numFormat purgeable again */
  383.         HSetState((Handle)*numFormat, hState);
  384.     }
  385.     
  386.     if(*itlHandle == nil) {
  387.         errCode = ResError();
  388.         return((errCode != noErr) ? errCode : resNotFound);
  389.     }
  390. }
  391.  
  392. void InitItlRsrcs(verbPtr, idPtr)
  393.  
  394. short *verbPtr;    /* Array of Script Manager verbs that are changed */
  395. short *idPtr;    /* Array of original values */
  396.  
  397. {
  398.     short counter;
  399.  
  400.     *verbPtr++ = smScriptNumber;    /* Verb for 'itl0' resource */
  401.     *verbPtr++ = smScriptDate;        /* Verb for 'itl1' resource */
  402.     *verbPtr = smScriptToken;        /* Verb for 'itl4' resource */
  403.  
  404.     verbPtr -= 2;
  405.     for(counter = 3; --counter != -1; ) {
  406.     
  407.         /* Get the current setting and save it in the array of ID's */
  408.         *idPtr++ = (short)GetScriptVariable(smCurrentScript, *verbPtr);
  409.         
  410.         /* Set to use our international resources */
  411.         SetScriptVariable(smCurrentScript, *verbPtr++, INTL_RESOURCES);
  412.     }
  413.     ClearIntlResourceCache();
  414. }
  415.  
  416. void ResetItlRsrcs(verbPtr, idPtr)
  417.  
  418. short *verbPtr;    /* Array of Script Manager verbs that have been changed */
  419. short *idPtr;    /* Array of original values */
  420.  
  421. {
  422.     short counter;
  423.  
  424.     for(counter = 3; --counter != -1; ) {
  425.     
  426.         /* Set the Script Manager values back to what they were */
  427.         SetScriptVariable(smCurrentScript, *verbPtr++, *idPtr++);
  428.     }
  429.     ClearIntlResourceCache();
  430. }